#include <stdlib.h>
#include <stdio.h>
#include <nds.h>
#include <unistd.h> // for chdir()
#include <ctype.h>  // for tolower()
#include <stdarg.h> // for va_list
#include <string.h> // for stricmp()

#include "common.h"

#include "filelist.h"

#define WAIT()  do { \
                    printf("\nWAIT@%s(%s):%d", basename(__FILE__), __func__, __LINE__); \
                    scanKeys(); \
                    while(!(keysDown() & KEY_LEFT)) swiWaitForVBlank(); \
                } while(0)

inline void _fl_memswap(sFile *a, sFile *b);    // swap the position of two elements in the memory but not in the list

#define S(f)         ( ((f)>>1)&63 )    /* 6 bits */
#define S_MODE(f)    ( (f)&(7<<0) )
#define S_INVERT(f)  ( (f)&(1<<3) )
#define S_DIRS(f)    ( (f)&(3<<4) )

sFile *fl_getcwd(u32 flags, ...) {
    sFile *fl;  // the file list
    fcmphandler fch=NULL;   // the comparator handler

    // in some cases be sure that you stat the file before
    if(S_MODE(S(flags))==BY_SIZE || S_DIRS(S(flags))!=DIRS_ASFILES) flags|=FL_STAT;

    // read directory
    fl=fl_readcwd(flags);
    if(!fl) return NULL;

    // get the handler pointer (the argument after flags)
    if(S_MODE(S(flags))==BY_HANDLER) {
        va_list ap;
        va_start(ap, flags);
        fch=va_arg(ap, fcmphandler);
        va_end(ap);
    }

    // sort file list
    if(fl_sort(fl, S(flags), fch)) {
        fl_free(fl);
        return NULL;
    }

    return fl;
}

sFile *fl_readcwd(u32 flags) {
    DIR *cwd;   // current working directory
    struct dirent *cde; // current directory entry
    sFile *cur=NULL, *first=NULL;

    cwd=opendir(".");
    if(!cwd) return NULL;

    while((cde=readdir(cwd))) {
        if(cur) {
            cur->next=(sFile *)xmalloc(sizeof(sFile));
            cur->next->prev=cur;

            cur=cur->next;
        }
        else {
            cur=(sFile *)xmalloc(sizeof(sFile));
            cur->prev=NULL;

            first=cur;
        }
        cur->next=NULL;
        strncpy(cur->name, cde->d_name, 256);
        cur->nameh=fl_hash(cur->name);    // fill cur->nameh and cur->namel (yes !)
        if((flags&FL_STAT) && stat(cde->d_name, &cur->st)) {   // fill cur->st
            fl_free(first);
            first=NULL;
            break;
        }
    }

    closedir(cwd);

    return first;
}

sFile *_gnp_nothing(u32 flags, sFile *first, sFile *cur, fcmphandler fch) { // don't sort the list
    sFile *fi;

    switch(S_DIRS(flags)) {
    case DIRS_ASFILES:  // don't worry if that's a directory or not
        if(S_INVERT(flags)) {   // return the first element of the list
            return first;
        }
        else {  // don't move this file
            return cur;
        }

    case DIRS_BEFORE:   // always place the directories before the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) {   // return the first element of the list
                return first;
            }
            else {  // return the first regular file
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode); fi=fi->next);
                return fi;
            }
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) {   // return the first regular file
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode); fi=fi->next);
                return fi;
            }
            else {  // don't move this file
                return cur;
            }
        }

    case DIRS_AFTER:   // always place the directories after the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) {   // return the first directory
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode); fi=fi->prev);
                return fi;
            }
            else {  // don't move this files
                return cur;
            }
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) {   // return the first element of the list
                return first;
            }
            else {  // return the first directory
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode); fi=fi->prev);
                return fi;
            }
        }
    }

    return cur;
}

sFile *_gnp_size(u32 flags, sFile *first, sFile *cur, fcmphandler fch) {    // sort the list by size
    sFile *fi;
    off_t cur_size=cur->st.st_size;

    switch(S_DIRS(flags)) {
    case DIRS_ASFILES:  // don't worry if that's a directory or not
        if(S_INVERT(flags)) // the biggest first
            for(fi=cur; fi->prev && fi->prev->st.st_size<=cur_size; fi=fi->prev);
        else    // the smallest first
            for(fi=cur; fi->prev && fi->prev->st.st_size>cur_size; fi=fi->prev);
        return fi;

    case DIRS_BEFORE:   // always place the directories before the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) {   // return the first element of the list
                return first;
            }
            else {  // return the first regular file
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode); fi=fi->next);
                return fi;
            }
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && fi->prev->st.st_size<=cur_size; fi=fi->prev);
            else    // the smallest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && fi->prev->st.st_size>cur_size; fi=fi->prev);
            return fi;
        }

    case DIRS_AFTER:   // always place the directories after the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) {   // return the first directory
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode); fi=fi->prev);
                return fi;
            }
            else {  // don't move this files
                return cur;
            }
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && fi->st.st_size>cur_size; fi=fi->next);
            else    // the smallest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && fi->st.st_size<=cur_size; fi=fi->next);
            return fi;
        }
    }

    return cur;
}

sFile *_gnp_mtime(u32 flags, sFile *first, sFile *cur, fcmphandler fch) {    // sort the list by modified time
    sFile *fi;
    time_t cur_mtime=cur->st.st_mtime;

    switch(S_DIRS(flags)) {
    case DIRS_ASFILES:  // don't worry if that's a directory or not
        if(S_INVERT(flags)) // the biggest first
            for(fi=cur; fi->prev && fi->prev->st.st_mtime<=cur_mtime; fi=fi->prev);
        else    // the smallest first
            for(fi=cur; fi->prev && fi->prev->st.st_mtime>cur_mtime; fi=fi->prev);
        return fi;

    case DIRS_BEFORE:   // always place the directories before the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) // the biggest first
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode) && fi->st.st_mtime>cur_mtime; fi=fi->next);
            else    // the smallest first
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode) && fi->st.st_mtime<=cur_mtime; fi=fi->next);
            return fi;
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && fi->prev->st.st_mtime<=cur_mtime; fi=fi->prev);
            else    // the smallest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && fi->prev->st.st_mtime>cur_mtime; fi=fi->prev);
            return fi;
        }

    case DIRS_AFTER:   // always place the directories after the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) // the biggest first
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode) && fi->prev->st.st_mtime<=cur_mtime; fi=fi->prev);
            else    // the smallest first
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode) && fi->prev->st.st_mtime>cur_mtime; fi=fi->prev);
            return fi;
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && fi->st.st_mtime>cur_mtime; fi=fi->next);
            else    // the smallest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && fi->st.st_mtime<=cur_mtime; fi=fi->next);
            return fi;
        }
    }

    return cur;
}

sFile *_gnp_name(u32 flags, sFile *first, sFile *cur, fcmphandler fch) {    // sort the list by name
    sFile *fi;
    char *cur_name=cur->name;

    switch(S_DIRS(flags)) {
    case DIRS_ASFILES:  // don't worry if that's a directory or not
        if(S_INVERT(flags)) // the biggest first
            for(fi=cur; fi->prev && stricmp(fi->prev->name, cur_name)<=0; fi=fi->prev);
        else    // the smallest first
            for(fi=cur; fi->prev && stricmp(fi->prev->name, cur_name)>0; fi=fi->prev);
        return fi;

    case DIRS_BEFORE:   // always place the directories before the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) // the biggest first
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode) && stricmp(fi->name, cur_name)>0; fi=fi->next);
            else    // the smallest first
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode) && stricmp(fi->name, cur_name)<=0; fi=fi->next);
            return fi;
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && stricmp(fi->prev->name, cur_name)<=0; fi=fi->prev);
            else    // the smallest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && stricmp(fi->prev->name, cur_name)>0; fi=fi->prev);
            return fi;
        }

    case DIRS_AFTER:   // always place the directories after the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) // the biggest first
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode) && stricmp(fi->prev->name, cur_name)<=0; fi=fi->prev);
            else    // the smallest first
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode) && stricmp(fi->prev->name, cur_name)>0; fi=fi->prev);
            return fi;
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && stricmp(fi->name, cur_name)>0; fi=fi->next);
            else    // the smallest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && stricmp(fi->name, cur_name)<=0; fi=fi->next);
            return fi;
        }
    }

    return cur;
}

sFile *_gnp_handler(u32 flags, sFile *first, sFile *cur, fcmphandler fch) {    // sort the list by user handler
    sFile *fi;

    switch(S_DIRS(flags)) {
    case DIRS_ASFILES:  // don't worry if that's a directory or not
        if(S_INVERT(flags)) // the biggest first
            for(fi=cur; fi->prev && fch(fi->prev, cur)<=0; fi=fi->prev);
        else    // the smallest first
            for(fi=cur; fi->prev && fch(fi->prev, cur)>0; fi=fi->prev);
        return fi;

    case DIRS_BEFORE:   // always place the directories before the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) // the biggest first
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode) && fch(fi, cur)>0; fi=fi->next);
            else    // the smallest first
                for(fi=first; fi!=cur && !S_ISREG(fi->st.st_mode) && fch(fi, cur)<=0; fi=fi->next);
            return fi;
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && fch(fi->prev, cur)<=0; fi=fi->prev);
            else    // the smallest first
                for(fi=cur; fi->prev && S_ISREG(fi->prev->st.st_mode) && fch(fi->prev, cur)>0; fi=fi->prev);
            return fi;
        }

    case DIRS_AFTER:   // always place the directories after the regular files
        if(!S_ISREG(cur->st.st_mode)) { // the current file is a folder
            if(S_INVERT(flags)) // the biggest first
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode) && fch(fi->prev, cur)<=0; fi=fi->prev);
            else    // the smallest first
                for(fi=cur; fi->prev && !S_ISREG(fi->prev->st.st_mode) && fch(fi->prev, cur)>0; fi=fi->prev);
            return fi;
        }
        else {  // the current file is a regular file
            if(S_INVERT(flags)) // the biggest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && fch(fi, cur)>0; fi=fi->next);
            else    // the smallest first
                for(fi=first; fi!=cur && S_ISREG(fi->st.st_mode) && fch(fi, cur)<=0; fi=fi->next);
            return fi;
        }
    }

    return cur;
}

int fl_sort(sFile *fl_input, u32 flags, ...) {
    if(!fl_input) return 1;
    if(!flags) return 0;

    fcmphandler fch=NULL;   // the comparator handler
    sFile *(*getnewplace)(u32 flags, sFile *first, sFile *cur, fcmphandler fch)=NULL; // mustn't return NULL;

    switch(S_MODE(flags)) {
    case BY_NOTHING:
        getnewplace=_gnp_nothing;
        break;
    case BY_SIZE:
        getnewplace=_gnp_size;
        break;
    case BY_MTIME:
        getnewplace=_gnp_mtime;
        break;
    case BY_NAME:
        getnewplace=_gnp_name;
        break;
    case BY_HANDLER:
        {   // get the pointer of the handler function
            va_list ap;
            va_start(ap, flags);
            fch=va_arg(ap, fcmphandler);
            va_end(ap);
        }
        getnewplace=_gnp_handler;
        break;
    default:
        getnewplace=_gnp_nothing;
        break;
    }

    sFile *first=fl_input;
    while(first->prev) first=first->prev;   // first must be the first element

    sFile *lli, *llj=first->next, *nj, *tmp;
    while(llj) {
        lli=getnewplace(flags, first, llj, fch);    // get the new place of llj in the list

        nj=llj->next;
        if(lli!=llj) {  // put llj just before lli
            llj->prev->next=llj->next;
            if(lli->prev) lli->prev->next=llj;
            tmp=llj->prev;
            llj->prev=lli->prev;
            lli->prev=llj;
            if(llj->next) llj->next->prev=tmp;
            llj->next=lli;

            if(first->prev) first=first->prev;   // be sure to always have the first file in first
        }
        llj=nj;
    }

    _fl_memswap(first, fl_input);   // fl_input point now to the first element of the list

    return 0;
}

inline void _fl_memswap(sFile *a, sFile *b) {   // XXX : tested only when 'a' is the first element of the list and 'b' an element of the same list
    if(a==b) return;

    sFile tp;
    memcpy(&tp, a, sizeof(sFile));
    memcpy(a, b, sizeof(sFile));
    memcpy(b, &tp, sizeof(sFile));
    if(b->next==b) {
        b->next=a;
        a->prev=b;
    }
    else {
        b->next->prev=b;
        a->prev->next=a;
    }
    if(a->next) a->next->prev=a;
}

u32 fl_hash(char *fn) { // case insensitive
    u16 len=0, _h=0xDEAD, h[2];

    while(*fn) {
        _h^=tolower(*fn);
        _h=(_h>>6) | (_h<<10);

        len++;
        fn++;
    }

    h[0]=len;
    h[1]=_h;
    return *(u32 *)h;
}

void fl_free(sFile *fl) {
    if(!fl) return;

    while(fl->prev) fl=fl->prev;

    sFile *next;
    while(fl) {
        next=fl->next;
        xfree(fl);
        fl=next;
    }
}

